package com.chenwc.minio;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.chenwc.common.fileUtils.ReadFile;
import com.chenwc.common.fileUtils.WriteFile;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.*;
import okhttp3.Headers;
import okhttp3.MediaType;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class MinioUtil {
    private static final Logger logger = LoggerFactory.getLogger(MinioUtil.class);

    /**
     * MinioClient
     */
    private static MinioClient minioClient = null;

    private static String endpoint;
    private static String accessKey;
    private static String secretKey;

    private static String region;

    /**
     * 每次查询的最大key数量
     */
    private static final Integer MAX_KEY_PER_PAGE = 10;

    /**
     * 文件分块大小,10M
     */
    private static final Integer CHUNK_FILE_SIZE = 10 * 1024 * 1024;

    /**
     * 文件分块阈值,10M
     */
    private static final Integer CHUNK_FILE_SIZE_THRESHOLD = 10 * 1024 * 1024;

    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    public static void getMinioClient(String userEndpoint, String userAccessKey, String userSecretKey, String userRegion) {
        endpoint = userEndpoint;
        accessKey = userAccessKey;
        secretKey = userSecretKey;
        region = userRegion;
    }

    /**
     * 获取MinioClient
     */
    public static void getMinioClient() {
        if (null == minioClient) {
            try {
                minioClient =
                        MinioClient.builder()
                                .endpoint(endpoint)
                                .region(region)
                                .credentials(accessKey, secretKey)
                                .build();
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }
    }

    public static void close() {
        if (null != minioClient) {
            try {
                System.exit(0);
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }
    }

    /**
     * 判断bucket是否存在
     *
     * @param bucketName bucket名称
     * @return boolean
     */
    public static boolean bucketExists(String bucketName) {
        try {
            getMinioClient();
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return false;
    }

    /**
     * 删除bucket的加密配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteBucketEncryption(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteBucketEncryption(
                    DeleteBucketEncryptionArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除bucket的生命周期配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteBucketLifecycle(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteBucketLifecycle(
                    DeleteBucketLifecycleArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除bucket的标签配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteBucketTags(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteBucketTags(DeleteBucketTagsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除bucket的策略配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteBucketPolicy(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteBucketPolicy(DeleteBucketPolicyArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除bucket的复制配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteBucketReplication(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteBucketReplication(
                    DeleteBucketReplicationArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除bucket的通知配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteBucketNotification(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteBucketNotification(
                    DeleteBucketNotificationArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除bucket的对象锁配置
     *
     * @param bucketName bucket名称
     */
    public static void deleteObjectLockConfiguration(String bucketName) {
        try {
            getMinioClient();
            minioClient.deleteObjectLockConfiguration(
                    DeleteObjectLockConfigurationArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 获取bucket的加密配置
     *
     * @param bucketName bucket名称
     * @return SseConfiguration
     */
    public static SseConfiguration getBucketEncryption(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketEncryption(
                    GetBucketEncryptionArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的生命周期配置
     *
     * @param bucketName bucket名称
     * @return LifecycleConfiguration
     */
    public static LifecycleConfiguration getBucketLifecycle(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketLifecycle(
                    GetBucketLifecycleArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的通知配置
     *
     * @param bucketName bucket名称
     * @return NotificationConfiguration
     */
    public static NotificationConfiguration getBucketNotification(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketNotification(
                    GetBucketNotificationArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的策略配置
     *
     * @param bucketName bucket名称
     * @return String
     */
    public static String getBucketPolicy(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的复制配置
     *
     * @param bucketName bucket名称
     * @return ReplicationConfiguration
     */
    public static ReplicationConfiguration getBucketReplication(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketReplication(
                    GetBucketReplicationArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的标签配置
     *
     * @param bucketName bucket名称
     * @return Tags
     */
    public static Tags getBucketTags(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketTags(GetBucketTagsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的版本控制配置
     *
     * @param bucketName bucket名称
     * @return VersioningConfiguration
     */
    public static VersioningConfiguration getBucketVersioning(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getBucketVersioning(
                    GetBucketVersioningArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取bucket的对象锁配置
     *
     * @param bucketName bucket名称
     * @return ObjectLockConfiguration
     */
    public static ObjectLockConfiguration getObjectLockConfiguration(String bucketName) {
        try {
            getMinioClient();
            return minioClient.getObjectLockConfiguration(
                    GetObjectLockConfigurationArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取所有bucket
     *
     * @return List<Bucket>
     */
    public static List<Bucket> listBuckets() {
        try {
            getMinioClient();
            return minioClient.listBuckets();
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 监听bucket的事件通知
     *
     * @param bucketName bucket名称
     * @param prefix     前缀
     * @param suffix     后缀
     * @param events     事件
     */
    public static void listenBucketNotification(String bucketName, String prefix, String suffix, String[] events) {
        getMinioClient();
        try (CloseableIterator<Result<NotificationRecords>> ci =
                     minioClient.listenBucketNotification(
                             ListenBucketNotificationArgs.builder()
                                     .bucket(bucketName)
                                     .prefix(prefix)
                                     .suffix(suffix)
                                     .events(events)
                                     .build())) {
            while (ci.hasNext()) {
                NotificationRecords records = ci.next().get();
                for (Event event : records.events()) {
                    logger.info("Event " + event.eventType() + " occurred at " + event.eventTime()
                            + " for " + event.bucketName() + "/" + event.objectName());
                }
            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 判断对象是否存在
     *
     * @param bucketName bucket名称
     * @param key        对象key
     * @return 存在返回true，否则返回false
     */
    public static boolean isExistObject(String bucketName, String key) {
        try {
            getMinioClient();
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(key)
                            .includeVersions(false)
                            .recursive(true)
                            .build());
            if (results != null) {
                if (!results.iterator().hasNext()) {
                    logger.info("bucket: {} is empty", bucketName);
                    return false;
                }
                for (Result<Item> result : results) {
                    Item item = null;
                    try {
                        item = result.get();
                    } catch (Exception e) {
                        logger.error("Error occurred: ", e);
                    }
                    if (item != null && key.equals(item.objectName())) {
                        return true;
                    }
                }
            } else {
                logger.info("bucket: {} is empty", bucketName);
                return false;
            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return false;
    }

    /**
     * 获取bucket所有对象
     *
     * @param bucketName      bucket名称
     * @param prefix          前缀
     * @param recursive       是否递归获取所有对象
     * @param includeVersions 是否包含版本
     * @return JSONArray
     */
    public static JSONArray listObjects(String bucketName, String prefix, boolean recursive,
                                        boolean includeVersions) {
        JSONArray resultJson = new JSONArray();
        try {
            getMinioClient();
            VersioningConfiguration versioningConfiguration = getBucketVersioning(bucketName);
            if (versioningConfiguration != null) {
                if (StringUtils.isEmpty(versioningConfiguration.status().toString())) {
                    logger.info("存储桶未开启版本控制，includeVersions 自动设置为false！");
                    includeVersions = false;
                }
            }
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix)
                            .includeVersions(includeVersions)
                            .recursive(recursive)
                            .build());
            for (Result<Item> result : results) {
                Item item = result.get();
                payloadObjectMeta(resultJson, item);
            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return resultJson;
    }

    /**
     * item 对象元信息转换成JSON
     *
     * @param resultJson JSONArray
     * @param item       对象元信息
     */
    private static void payloadObjectMeta(JSONArray resultJson, Item item) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("versionId", item.versionId());
        jsonObject.put("size", item.size());
        if (!item.isDir()) {
            if (null != item.lastModified()) {
                LocalDateTime lastModified = item.lastModified().toLocalDateTime();
                jsonObject.put("lastModified", dateTimeFormatter.format(lastModified));
            } else {
                jsonObject.put("lastModified", "");
            }
            JSONObject owner = new JSONObject();
            owner.put("id", item.owner().id());
            owner.put("displayName", item.owner().displayName());
            jsonObject.put("owner", owner);
        } else {
            jsonObject.put("lastModified", "");
            jsonObject.put("owner", null);
        }
        jsonObject.put("isLatest", item.isLatest());
        jsonObject.put("isDeleteMarker", item.isDeleteMarker());
        jsonObject.put("objectName", item.objectName());
        if (null != item.etag()) {
            jsonObject.put("etag", item.etag().replace("\"", ""));
        } else {
            jsonObject.put("etag", "");
        }
        jsonObject.put("storageClass", item.storageClass());
        jsonObject.put("isDir", item.isDir());
        jsonObject.put("userMetadata", item.userMetadata());
        resultJson.add(jsonObject);
    }

    /**
     * 创建bucket
     *
     * @param bucketName bucket名称
     * @param region     bucket区域
     * @param objectLock 对象锁，对象锁只能在创建bucket时开启
     * @return boolean
     */
    public static boolean createBucket(String bucketName, String region, boolean objectLock) {
        try {
            getMinioClient();
            if (bucketExists(bucketName)) {
                return true;
            }
            if (StringUtils.isEmpty(region)) {
                // default region
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .objectLock(objectLock)
                        .build());
            }
            if (StringUtils.isNotEmpty(region)) {
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .region(region)
                        .objectLock(objectLock)
                        .build());
            }
            return bucketExists(bucketName);
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return false;
    }

    /**
     * 删除bucket
     *
     * @param bucketName bucket名称
     * @return boolean
     */
    public static boolean deleteBucket(String bucketName) {
        try {
            getMinioClient();
            if (!bucketExists(bucketName)) {
                return true;
            }
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            return !bucketExists(bucketName);
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return false;
    }

    /**
     * 设置bucket的加密配置
     *
     * @param bucketName bucket名称
     * @param config     加密配置
     */
    public static void setBucketEncryption(String bucketName, SseConfiguration config) {
        try {
            getMinioClient();
            minioClient.setBucketEncryption(
                    SetBucketEncryptionArgs.builder().bucket(bucketName).config(config).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的生命周期配置
     *
     * @param bucketName bucket名称
     * @param config     生命周期配置
     */
    public static void setBucketLifecycle(String bucketName, LifecycleConfiguration config) {
        try {
            getMinioClient();
            minioClient.setBucketLifecycle(
                    SetBucketLifecycleArgs.builder().bucket(bucketName).config(config).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的通知配置
     *
     * @param bucketName bucket名称
     * @param config     通知配置
     */
    public static void setBucketNotification(String bucketName, NotificationConfiguration config) {
        try {
            getMinioClient();
            minioClient.setBucketNotification(
                    SetBucketNotificationArgs.builder().bucket(bucketName).config(config).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的策略配置
     *
     * @param bucketName bucket名称
     * @param policy     策略配置
     */
    public static void setBucketPolicy(String bucketName, String policy) {
        try {
            getMinioClient();
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder().bucket(bucketName).config(policy).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的版本配置
     *
     * @param bucketName bucket名称
     * @param config     版本配置
     */
    public static void setBucketVersioning(String bucketName, VersioningConfiguration config) {
        try {
            getMinioClient();
            minioClient.setBucketVersioning(
                    SetBucketVersioningArgs.builder().bucket(bucketName).config(config).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的复制配置
     *
     * @param bucketName bucket名称
     * @param config     复制配置
     */
    public static void setBucketReplication(String bucketName, ReplicationConfiguration config) {
        try {
            getMinioClient();
            minioClient.setBucketReplication(
                    SetBucketReplicationArgs.builder().bucket(bucketName).config(config).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的标签配置
     *
     * @param bucketName bucket名称
     * @param tags       标签配置
     */
    public static void setBucketTags(String bucketName, Map<String, String> tags) {
        try {
            getMinioClient();
            minioClient.setBucketTags(SetBucketTagsArgs.builder().bucket(bucketName).tags(tags).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置bucket的对象锁配置
     *
     * @param bucketName bucket名称
     * @param config     对象锁配置
     */
    public static void setObjectLockConfiguration(String bucketName, ObjectLockConfiguration config) {
        try {
            getMinioClient();
            minioClient.setObjectLockConfiguration(
                    SetObjectLockConfigurationArgs.builder().bucket(bucketName).config(config).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 通过使用服务器端副本组合来自不同源对象的数据来创建对象。
     * 把多个不同的文件块合成一个文件
     *
     * @param bucketName bucket名称
     * @param objectName 对象名称
     * @param sources    源对象
     * @return ObjectWriteResponse
     */
    public static ObjectWriteResponse composeObject(String bucketName, String objectName, LinkedList<ComposeSource> sources) {
        try {
            getMinioClient();
            return minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .sources(sources)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 复制对象
     *
     * @param srcBucket         源存储桶
     * @param srcKey            源对象
     * @param destinationBucket 目标存储桶
     * @param destinationKey    目标对象
     */
    public static void copyObject(String srcBucket, String srcKey, String destinationBucket, String destinationKey) {
        try {
            getMinioClient();
            minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .bucket(destinationBucket)
                            .object(destinationKey)
                            .source(
                                    CopySource.builder()
                                            .bucket(srcBucket)
                                            .object(srcKey)
                                            .build())
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 删除对象的tags
     *
     * @param bucketName 桶名
     * @param key        key
     */
    public static void deleteObjectTags(String bucketName, String key) {
        try {
            getMinioClient();
            minioClient.deleteObjectTags(
                    DeleteObjectTagsArgs.builder().bucket(bucketName).object(key).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 禁用对对象的合法保留。
     *
     * @param bucketName 桶名
     * @param key        key
     */
    public static void disableObjectLegalHold(String bucketName, String key) {
        try {
            getMinioClient();
            minioClient.disableObjectLegalHold(
                    DisableObjectLegalHoldArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 启用对对象的合法保留。
     *
     * @param bucketName 桶名
     * @param key        key
     */
    public static void enableObjectLegalHold(String bucketName, String key) {
        try {
            getMinioClient();
            minioClient.enableObjectLegalHold(
                    EnableObjectLegalHoldArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 获取对象信息
     *
     * @param bucketName 桶名
     * @param key        key
     */
    public static JSONObject getObject(String bucketName, String key) {
        getMinioClient();
        // get object given the bucket and object name
        GetObjectResponse response = null;
        JSONObject resp = new JSONObject();
        try {
            response = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .build());
            resp.put("object", response.object());
            resp.put("bucket", response.bucket());
            resp.put("region", response.region());
            Headers headers = response.headers();
            JSONObject headersMap = new JSONObject();
            if (null != headers && headers.size() > 0) {
                // 获取响应头
                for (int i = 0; i < response.headers().size(); i++) {
                    headersMap.put(response.headers().name(i), response.headers().value(i));
                }
            }
            resp.put("headers", headersMap);

        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        } finally {
            try {
                if (null != response) {
                    response.close();
                }
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }
        return resp;
    }

    /**
     * 下载对象
     *
     * @param bucketName 桶名
     * @param key        key
     * @param filePath   待写入本地文件全路径+文件名
     */
    public static void downloadObject(String bucketName, String key, String filePath) {
        try {
            logger.info("保存文件路径：" + filePath);
            getMinioClient();
            // Download object given the bucket, object name and output file name
            minioClient.downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .filename(filePath)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 获取对象的保留配置
     *
     * @param bucketName 桶名
     * @param key        key
     * @param versionId  对象的版本ID
     * @return Retention
     */
    public static Retention getObjectRetention(String bucketName, String key, String versionId) {
        try {
            getMinioClient();
            // Object with version id.
            Retention retention =
                    minioClient.getObjectRetention(
                            GetObjectRetentionArgs.builder()
                                    .bucket(bucketName)
                                    .object(key)
                                    .versionId(versionId)
                                    .build());
            logger.info("mode: " + retention.mode() + "until: " + retention.retainUntilDate());
            return retention;
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取对象的标签
     *
     * @param bucketName 桶名
     * @param key        key
     * @return Tags
     */
    public static Tags getObjectTags(String bucketName, String key) {
        try {
            getMinioClient();
            return minioClient.getObjectTags(
                    GetObjectTagsArgs.builder().bucket(bucketName).object(key).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取对象的签名URL
     *
     * @param reqParams  请求头
     * @param bucketName 桶名
     * @param key        key
     * @param duration   URL过期时间
     * @param unit       过期时间的时间单位
     * @return URL
     */
    public static String getPresignedObjectUrl(Map<String, String> reqParams,
                                               String bucketName,
                                               String key,
                                               int duration,
                                               TimeUnit unit) {
        try {
            getMinioClient();
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(key)
                            .expiry(duration, unit)
                            .extraQueryParams(reqParams)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 获取对象的签名URL，默认半小时
     *
     * @param bucketName 桶名
     * @param key        key
     * @return URL
     */
    public static String getPresignedObjectUrl(String bucketName,
                                               String key) {
        try {
            getMinioClient();
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(key)
                            .expiry(30, TimeUnit.MINUTES)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 如果对对象启用了合法保留，则返回true。
     *
     * @param bucketName 桶名
     * @param key        key
     * @param versionId  版本ID
     * @return 如果对对象启用了合法保留，则返回true。
     */
    public static boolean isObjectLegalHoldEnabled(String bucketName, String key, String versionId) {
        try {
            getMinioClient();
            return minioClient.isObjectLegalHoldEnabled(
                    IsObjectLegalHoldEnabledArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .versionId(versionId)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return false;
    }

    /**
     * 获取要使用POST方法上载其数据的对象的PostPolicy的表单数据
     *
     * @param bucketName          桶名
     * @param key                 key
     * @param startsWithCondition Content-Type开头的条件。
     * @return 上载其数据的对象的PostPolicy的表单数据
     */
    public static Map<String, String> getPresignedPostFormData(String bucketName,
                                                               String key,
                                                               String startsWithCondition) {
        try {
            getMinioClient();
            // Create new post policy for 'my-bucketname' with 7 days expiry from now.
            PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(7));
            // Add condition that 'key' (object name) equals to 'my-objectname'.
            policy.addEqualsCondition("key", key);
            // Add condition that 'Content-Type' starts with 'image/'.
            policy.addStartsWithCondition("Content-Type", startsWithCondition);
            // Add condition that 'content-length-range' is between 64kiB to 10MiB.
            policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
            return minioClient.getPresignedPostFormData(policy);
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return null;
    }

    /**
     * 上传文件
     *
     * @param bucketName   桶名
     * @param key          key
     * @param file         文件
     * @param headers      请求头
     * @param userMetadata 用户元数据
     * @param contentType  请求体类型
     */
    public static void putObject(String bucketName,
                                 String key,
                                 File file,
                                 Map<String, String> headers,
                                 Map<String, String> userMetadata,
                                 MediaType contentType) {
        FileInputStream inputStream = null;
        try {
            getMinioClient();
            inputStream = new FileInputStream(file);
            if (null == headers) {
                headers = new HashMap<>();
            }
            if (null == userMetadata) {
                userMetadata = new HashMap<>();
            }
            minioClient.putObject(
                    PutObjectArgs.builder().bucket(bucketName).object(key).stream(
                                    inputStream, file.length(), -1)
                            .headers(headers)
                            .userMetadata(userMetadata)
                            .contentType(contentType.toString())
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        } finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }
    }

    /**
     * 上传对象
     *
     * @param bucketName 桶名
     * @param key        key
     * @param filePath   本地文件路径
     */
    public static void uploadObject(String bucketName,
                                    String key,
                                    String filePath) {
        try {
            getMinioClient();
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName).object(key).filename(filePath).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 在一个put调用中上载多个对象。这是通过创建中间的TAR文件来完成的，该文件被选择性地压缩，并上传到S3服务。
     *
     * @param bucketName 桶名
     * @param key        key
     * @param localFile  本地文件
     */
    public static void uploadSnowballObjects(String bucketName,
                                             String key,
                                             File localFile) {
        FileInputStream fis = null;
        try {
            getMinioClient();
            //文件分块数量
            int fileNum = (int) Math.ceil(localFile.length() * 1.0 / CHUNK_FILE_SIZE);
            fis = new FileInputStream(localFile);
            long available = localFile.length();
            logger.info("FileLength: {}", available);
            if (available <= 0L) {
                throw new RuntimeException("待读取文件为空文件，读取文件失败！");
            }
            //未达到分块文件阈值大小，走普通上传
            if (available <= CHUNK_FILE_SIZE_THRESHOLD) {
                logger.info("当前文件大小：{} 未达到分块文件阈值大小：{}，走普通上传！", available, CHUNK_FILE_SIZE_THRESHOLD);
                uploadObject(bucketName, key, localFile.getAbsolutePath());
                return;
            }
            byte[] result = new byte[(int) available];
            //读取总的字节数
            int readBytesNum = fis.read(result);
            // 上传数据, 这里上传 10 个 1M 的分块数据
            LinkedList<SnowballObject> objects = new LinkedList<>();
            LinkedList<File> files = new LinkedList<>();
            for (int i = 1; i <= fileNum; i++) {
                logger.info("正在拆分第：{} 个文件分片....", i);
                byte[] upload;
                if (i < fileNum) {
                    upload = new byte[CHUNK_FILE_SIZE];
                    //src 源数组，srcPos 从源数组哪个位置开始拷贝，dest 目标数组，destPos 从目标数组哪个位置开始复制，length 复制多少长度
                    int startIndex = (i - 1) * CHUNK_FILE_SIZE;
                    logger.debug("startIndex: {}", startIndex);
                    logger.debug("endIndex: {}", startIndex + CHUNK_FILE_SIZE);
                    System.arraycopy(result, startIndex, upload, 0, CHUNK_FILE_SIZE);
                }
                //最后一块
                else {
                    upload = new byte[(int) (available - CHUNK_FILE_SIZE * (i - 1))];
                    int startIndex = (i - 1) * CHUNK_FILE_SIZE;
                    int length = (int) (available - CHUNK_FILE_SIZE * (i - 1));
                    logger.debug("startIndex: {}", startIndex);
                    logger.debug("endIndex: {}", startIndex + length);
                    System.arraycopy(result, startIndex, upload, 0, length);
                }
                String path = localFile.getAbsolutePath().substring(0, localFile.getAbsolutePath().lastIndexOf(".") + 1)
                        + "z" + (i < 10 ? "0" + i : i);
                //写入本地分块文件
                WriteFile.writeFileByFileOutputStream(upload, path);
                File uploadFile = new File(path);
                files.add(uploadFile);
                objects.add(
                        new SnowballObject(
                                uploadFile.getName(),
                                Files.newInputStream(uploadFile.toPath()),
                                uploadFile.length(),
                                null));
            }
            logger.info("文件分块完成，开始上传分块文件....");
            //上传分块
            minioClient.uploadSnowballObjects(
                    UploadSnowballObjectsArgs.builder().bucket(bucketName).objects(objects).build());
            logger.info("分块上传完成，开始合并远程分块文件....");
            //创建合并对象
            LinkedList<ComposeSource> sourceObjectList = new LinkedList<>();
            for (File file : files) {
                sourceObjectList.add(
                        ComposeSource.builder()
                                .bucket(bucketName)
                                .object(file.getName())
                                .build());
            }
            composeObject(bucketName, key, sourceObjectList);
            logger.info("合并完成，删除本地分块文件和minio的分块文件....");
            //删除minio的分块文件
            VersioningConfiguration versioningConfiguration = getBucketVersioning(bucketName);
            if (versioningConfiguration != null &&
                    VersioningConfiguration.Status.ENABLED.equals(versioningConfiguration.status())) {
                for (File file : files) {
                    JSONArray versionList = getObjectVersion(bucketName, file.getName());
                    for (Object version : versionList) {
                        JSONObject versionJson = (JSONObject) version;
                        String versionId = versionJson.getString("versionId");
                        // Remove versioned object.
                        minioClient.removeObject(
                                RemoveObjectArgs.builder()
                                        .bucket(bucketName)
                                        .object(file.getName())
                                        .versionId(versionId)
                                        .build());
                    }
                    //删除本地分块文件
                    file.deleteOnExit();
                }
            } else {
                for (File file : files) {
                    removeObject(bucketName, file.getName(), null);
                    //删除本地分块文件
                    file.deleteOnExit();
                }
            }
            logger.info("删除本地分块文件和minio的分块文件完成....");
            logger.info("分块上传对象完成....");

        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        } finally {
            try {
                if (null != fis) {
                    fis.close();
                }
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }

    }

    /**
     * 获取对象版本
     *
     * @param bucketName 桶名
     * @param key        对象名
     * @return 对象版本列表
     */
    public static JSONArray getObjectVersion(String bucketName, String key) {
        JSONArray versionList = new JSONArray();
        try {
            getMinioClient();
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(key)
                            .maxKeys(MAX_KEY_PER_PAGE)
                            .includeVersions(true)
                            .build());
            if (results != null) {
                if (!results.iterator().hasNext()) {
                    logger.info("bucket: {} is empty", bucketName);
                    return versionList;
                }
                for (Result<Item> result : results) {
                    Item item = null;
                    try {
                        item = result.get();
                    } catch (Exception e) {
                        logger.error("Error occurred: ", e);
                    }
                    if (item != null && key.equals(item.objectName())) {
                        payloadObjectMeta(versionList, item);
                    }
                }
            } else {
                logger.info("bucket: {} is empty", bucketName);
                return versionList;
            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return versionList;
    }

    /**
     * 删除对象
     *
     * @param bucketName 桶名
     * @param key        key
     * @param versionId  版本ID，为空时忽略
     */
    public static void removeObject(String bucketName, String key, String versionId) {
        try {
            getMinioClient();
            if (StringUtils.isEmpty(versionId)) {
                // Remove object.
                minioClient.removeObject(
                        RemoveObjectArgs.builder().bucket(bucketName).object(key).build());
            } else {
                // Remove versioned object.
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(key)
                                .versionId(versionId)
                                .build());
            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 批量删除对象
     *
     * @param bucketName 桶名
     * @param keyList    key列表
     */
    public static void removeObjects(String bucketName, List<String> keyList) {
        try {
            getMinioClient();
            List<DeleteObject> objects = new LinkedList<>();
            for (String key : keyList) {
                objects.add(new DeleteObject(key));
            }
            Iterable<Result<DeleteError>> results =
                    minioClient.removeObjects(
                            RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
//            for (Result<DeleteError> result : results) {
//                DeleteError error = result.get();
//                logger.info(
//                        "Error in deleting object " + error.objectName() + "; " + error.message());
//            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 批量删除对象
     *
     * @param bucketName 桶名
     * @param keyList    key列表
     */
    public static void removeObjectsByVersionId(String bucketName, List<Map<String, String>> keyList) {
        try {
            getMinioClient();
            List<DeleteObject> objects = new LinkedList<>();
            for (Map<String, String> row : keyList) {
                objects.add(new DeleteObject(row.get("key"), row.get("versionId")));
            }
            Iterable<Result<DeleteError>> results =
                    minioClient.removeObjects(
                            RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
//            for (Result<DeleteError> result : results) {
//                DeleteError error = result.get();
//                logger.info(
//                        "Error in deleting object " + error.objectName() + "; " + error.message());
//            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 恢复对象
     *
     * @param bucketName 桶名
     * @param key        key
     * @param versionId  版本ID，为空时忽略
     */
    public static void restoreObject(String bucketName, String key, String versionId) {
        try {
            getMinioClient();
            if (StringUtils.isEmpty(versionId)) {
                // Restore object.
                minioClient.restoreObject(
                        RestoreObjectArgs.builder()
                                .bucket(bucketName)
                                .object(key)
                                .request(new RestoreRequest(null, null, null, null, null, null))
                                .build());
            } else {
                // Restore versioned object.
                minioClient.restoreObject(
                        RestoreObjectArgs.builder()
                                .bucket(bucketName)
                                .object(key)
                                .versionId(versionId)
                                .request(new RestoreRequest(null, null, null, null, null, null))
                                .build());
            }
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }
    /**
     * 通过SQL表达式选择JSON对象的内容
     * 对象必须是CSV、JSON或Parquet（*）格式。
     * UTF-8是Select API支持的唯一编码类型。
     * GZIP或BZIP2---CSV和JSON文件可以使用GZIP、BZIP2、ZSTD以及LZ4、S2和SNAPPY的流格式进行压缩。
     * Parquet API支持使用GZIP、Snappy、LZ4的柱状压缩。Parquet对象不支持全对象压缩。
     * 服务器端加密-Select API支持查询受服务器端加密保护的对象。
     *
     * @param bucketName      桶名
     * @param key             key
     * @param compressionType 对象压缩类型
     * @param jsonType        JSON类型
     * @param sqlExpression   SQL表达式
     */
    public static String selectJsonObjectContent(String bucketName, String key,
                                                 CompressionType compressionType,
                                                 JsonType jsonType,
                                                 String sqlExpression) {
        SelectResponseStream stream = null;
        ByteArrayOutputStream outStream = null;
        try {
            getMinioClient();
            InputSerialization is = new InputSerialization(compressionType, jsonType);
            OutputSerialization os = new OutputSerialization(null);
            stream = minioClient.selectObjectContent(
                    SelectObjectContentArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .sqlExpression(sqlExpression)
                            .inputSerialization(is)
                            .outputSerialization(os)
                            .requestProgress(true)
                            .build());

            outStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = stream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            byte[] result = outStream.toByteArray();
            //统计信息
            Stats stats = stream.stats();
            logger.info("bytes scanned: " + stats.bytesScanned());
            logger.info("bytes processed: " + stats.bytesProcessed());
            logger.info("bytes returned: " + stats.bytesReturned());
            return new String(result, StandardCharsets.UTF_8);
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        } finally {
            try {
                if (null != stream) {
                    stream.close();
                }
                if (null != outStream) {
                    outStream.close();
                }
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }
        return null;
    }

    /**
     * 通过SQL表达式选择CSV对象的内容
     * 对象必须是CSV、JSON或Parquet（*）格式。
     * UTF-8是Select API支持的唯一编码类型。
     * GZIP或BZIP2---CSV和JSON文件可以使用GZIP、BZIP2、ZSTD以及LZ4、S2和SNAPPY的流格式进行压缩。
     * Parquet API支持使用GZIP、Snappy、LZ4的柱状压缩。Parquet对象不支持全对象压缩。
     * 服务器端加密-Select API支持查询受服务器端加密保护的对象。
     *
     * @param bucketName      桶名
     * @param key             key
     * @param compressionType 对象压缩类型
     * @param sqlExpression   SQL表达式
     */
    public static String selectCsvObjectContent(String bucketName, String key, CompressionType compressionType, String sqlExpression) {
        SelectResponseStream stream = null;
        ByteArrayOutputStream outStream = null;
        try {
            getMinioClient();
            InputSerialization is = new InputSerialization(compressionType,
                    false,
                    null,
                    null,
                    FileHeaderInfo.USE,
                    null,
                    null,
                    null);
            OutputSerialization os = new OutputSerialization(null,
                    null,
                    null,
                    QuoteFields.ASNEEDED,
                    null);
            stream = minioClient.selectObjectContent(
                    SelectObjectContentArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .sqlExpression(sqlExpression)
                            .inputSerialization(is)
                            .outputSerialization(os)
                            .requestProgress(true)
                            .build());

            outStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = stream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            byte[] result = outStream.toByteArray();
            //统计信息
            Stats stats = stream.stats();
            logger.info("bytes scanned: " + stats.bytesScanned());
            logger.info("bytes processed: " + stats.bytesProcessed());
            logger.info("bytes returned: " + stats.bytesReturned());
            return new String(result, StandardCharsets.UTF_8);
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        } finally {
            try {
                if (null != stream) {
                    stream.close();
                }
                if (null != outStream) {
                    outStream.close();
                }
            } catch (Exception e) {
                logger.error("Error occurred: ", e);
            }
        }
        return null;
    }

    /**
     * 设置对象的保留配置
     *
     * @param bucketName 桶名
     * @param key        key
     * @param retention  对象的保留配置
     */
    public static void setObjectRetention(String bucketName, String key, Retention retention) {
        try {
            getMinioClient();
            minioClient.setObjectRetention(
                    SetObjectRetentionArgs.builder()
                            .bucket(bucketName)
                            .object(key)
                            .config(retention)
                            .bypassGovernanceMode(true)
                            .build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 设置对象的标签
     *
     * @param bucketName 桶名
     * @param key        key
     * @param tags       标签
     */
    public static void setObjectTags(String bucketName, String key, Map<String, String> tags) {
        try {
            getMinioClient();
            minioClient.setObjectTags(
                    SetObjectTagsArgs.builder().bucket(bucketName).object(key).tags(tags).build());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
    }

    /**
     * 获取对象的对象信息和元数据
     *
     * @param bucketName 桶名
     * @param key        key
     * @param versionId  版本ID，为空时忽略
     */
    public static JSONObject statObject(String bucketName, String key, String versionId) {
        JSONObject resp = new JSONObject();
        try {
            getMinioClient();
            StatObjectResponse objectStat;
            if (StringUtils.isEmpty(versionId)) {
                objectStat =
                        minioClient.statObject(
                                StatObjectArgs.builder().bucket(bucketName).object(key).build());
            } else {
                // Get information of a versioned object.
                objectStat =
                        minioClient.statObject(
                                StatObjectArgs.builder()
                                        .bucket(bucketName)
                                        .object(key)
                                        .versionId(versionId)
                                        .build());
            }
            resp.put("etag", objectStat.etag());
            resp.put("size", objectStat.size());
            if (null != objectStat.lastModified()) {
                LocalDateTime lastModified = objectStat.lastModified().toLocalDateTime();
                resp.put("lastModified", dateTimeFormatter.format(lastModified));
            } else {
                resp.put("lastModified", "");
            }
            resp.put("retentionMode", objectStat.retentionMode());
            resp.put("retentionRetainUntilDate", objectStat.retentionRetainUntilDate());
            resp.put("legalHold", objectStat.legalHold().status());
            resp.put("deleteMarker", objectStat.deleteMarker());
            resp.put("userMetadata", objectStat.userMetadata());
        } catch (Exception e) {
            logger.error("Error occurred: ", e);
        }
        return resp;
    }
}
